package service

import (
	"context"
	"database/sql"

	dbmodel "gitee.com/hp-huiw/family-expense-api/internal/db/model"
	"gitee.com/hp-huiw/family-expense-api/internal/dto"
	"gitee.com/hp-huiw/family-expense-api/internal/repository"
	"gitee.com/hp-huiw/my-go-core/date"
	"gitee.com/hp-huiw/my-go-core/security"
	"gitee.com/hp-huiw/my-go-core/util"
)

type AccountGroupService interface {
	QueryInfo(ctx context.Context, accountGroupID string, userID string) (*dto.AccountGroupResponse, error)
	Register(ctx context.Context, username string, password string, fullName string) error
	Unregister(ctx context.Context, accountGroupID string, username string) error
	Update(ctx context.Context, data *dto.AccountGroupRequest) error
}

// NewAccountGroupService creates a new AccountGroupService.
func NewAccountGroupService(db *sql.DB) AccountGroupService {
	return &accountGroupService{
		db:                      db,
		accountGroupRepository:  repository.NewAccountGroupRepository(db),
		accountGroupUserService: NewAccountGroupUserService(db),
		userService:             NewUserService(db),
		userRoleService:         NewUserRoleService(db),
	}
}

// QueryInfo queries user's account group info by user account group id.
func (s *accountGroupService) QueryInfo(ctx context.Context, accountGroupID string, userID string) (*dto.AccountGroupResponse, error) {
	owner, err := s.accountGroupUserService.QueryAccountGroupOwner(ctx, accountGroupID)

	if err != nil {
		return nil, err
	}

	return &dto.AccountGroupResponse{
		ID:            accountGroupID,
		IsOwner:       owner.ID == userID,
		OwnerID:       owner.ID,
		OwnerUsername: owner.Username,
		OwnerFullName: owner.FullName,
	}, nil
}

// Register creates a new AccountGroup and a new User.
// This typically used by new user register.
// During register the username and password are only needed.
// The other details can be added later.
func (s *accountGroupService) Register(ctx context.Context, username string, password string, fullName string) error {
	// Gets the ACCOUNT_GROUP_ADMIN role id.
	roleID, _ := NewRoleService(s.db).QueryID(ctx, security.RoleAccountGroupAdmin)

	// To perform a set of operations within a transaction.
	tx, err := s.db.Begin()

	if err != nil {
		return err
	}

	defer tx.Rollback()

	// Creates a new AccountGroup which will be used by the new User.
	accountGroup := &dbmodel.AccountGroup{
		ID:          util.GenerateUUIDWithoutDashes(),
		Title:       "我的账户",
		DateCreated: date.GetCurrentDate(),
		LastUpdated: date.GetCurrentDate(),
	}

	if err := s.accountGroupRepository.InsertWithTx(ctx, tx, accountGroup); err != nil {
		return err
	}

	// Prepares the default income/expense detail type for the created account group.
	if err = NewIncomeExpenseDetailTypeService(s.db).InitializeWithTx(ctx, tx, accountGroup.ID); err != nil {
		return err
	}

	// Creates a new User with the created AccountGroup and username, password.
	// Assigns the default role to the created user.
	user := &dbmodel.User{
		AccountGroupID:  util.AnyPtr(accountGroup.ID),
		Username:        username,
		Password:        password,
		FullName:        fullName,
		Enabled:         true,
		AccountLocked:   false,
		AccountExpired:  false,
		PasswordExpired: false,
	}

	if err = s.userService.InsertWithTx(ctx, tx, user); err != nil {
		return err
	}

	// Defines relationship between account group and user.
	if err = s.accountGroupUserService.InsertWithTx(ctx, tx, &dbmodel.AccountGroupUser{
		AccountGroupID:        accountGroup.ID,
		UserID:                user.ID,
		IsAccountGroupCreator: true,
	}); err != nil {
		return err
	}

	// Assigns the default role to the created user.
	if err = s.userRoleService.InsertWithTx(ctx, tx, &dbmodel.UserRole{
		AccountGroupID: accountGroup.ID,
		UserID:         user.ID,
		RoleID:         roleID,
	}); err != nil {
		return err
	}

	return tx.Commit()
}

// IMPORTANT!!!
// THIS ACTION CANNOT BE UNDONE.
// Unregister clears account group, including users and all data.
func (s *accountGroupService) Unregister(ctx context.Context, userID string, username string) error {
	// Queries user's avatar filename.
	avatarFilename, err := s.userService.QueryAvatarFilename(ctx, userID)

	if err != nil {
		return err
	}

	// Queries user's own account group id.
	accountGroupID, err := s.accountGroupUserService.QueryIDByCreator(ctx, userID)

	if err != nil {
		return err
	}

	tx, err := s.db.Begin()

	if err != nil {
		return err
	}

	defer tx.Rollback()

	// All users with this account group as their current account group will be restored to their own.
	if err = NewUserService(s.db).RestoreAccountGroupByAccountGroupIDWithTx(ctx, tx, accountGroupID); err != nil {
		return err
	}

	// Deletes active tokens which currently logged in to this account group to force re-login.
	if err = NewUserActiveTokenService(s.db).DeleteByAccountGroupWithTx(ctx, tx, accountGroupID); err != nil {
		return err
	}

	// Removes current user from all other account groups.
	// Removes all users from this account group.
	if err = s.accountGroupUserService.DeleteByAccountGroupAndUserWithTx(ctx, tx, accountGroupID, userID); err != nil {
		return err
	}

	// Deletes all journey income / expense.
	if err = NewJourneyIncomeExpenseDetailService(s.db).DeleteWithTx(ctx, tx, accountGroupID); err != nil {
		return err
	}

	// Deletes all journeies.
	if err = NewJourneyService(s.db).DeleteWithTx(ctx, tx, accountGroupID); err != nil {
		return err
	}

	// Deletes all income / expense.
	if err = NewIncomeExpenseDetailService(s.db).DeleteWithTx(ctx, tx, accountGroupID); err != nil {
		return err
	}

	// Deletes all income / expense detail types.
	if err = NewIncomeExpenseDetailTypeService(s.db).DeleteWithTx(ctx, tx, accountGroupID); err != nil {
		return err
	}

	// Deletes all bank cards.
	if err = NewBankCardService(s.db).DeleteWithTx(ctx, tx, accountGroupID); err != nil {
		return err
	}

	// Deletes all user roles.
	if err = s.userRoleService.DeleteInOrWithTx(ctx, tx, accountGroupID, userID); err != nil {
		return err
	}

	// Deletes all its pending requests.
	if err = NewJoinAccountGroupRequestService(s.db).DeleteWithTx(ctx, tx, accountGroupID, userID); err != nil {
		return err
	}

	// Deletes user.
	if err = s.userService.DeleteWithTx(ctx, tx, userID); err != nil {
		return err
	}

	// Deletes account group.
	if err = s.accountGroupRepository.DeleteWithTx(ctx, tx, accountGroupID); err != nil {
		return err
	}

	if avatarFilename != nil && !util.IsEmptyString(*avatarFilename) {
		NewFileService(s.db).Delete(*avatarFilename)
	}

	return tx.Commit()
}

// Update updates account group by id.
func (s *accountGroupService) Update(ctx context.Context, data *dto.AccountGroupRequest) error {
	model := dbmodel.AccountGroup{
		ID:          data.ID,
		Title:       data.Title,
		LastUpdated: date.GetCurrentDate(),
	}

	return s.accountGroupRepository.Update(ctx, &model)
}

// Container of AccountGroupService functions.
type accountGroupService struct {
	db                      *sql.DB
	accountGroupRepository  repository.AccountGroupRepository
	accountGroupUserService AccountGroupUserService
	userService             UserService
	userRoleService         UserRoleService
}
